//============================================================================
// Name        : git-r-us.cpp
// Author      : hp
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C, Ansi-style
//============================================================================

#include <iostream>
#include <string>
#include <sstream>
#include <stdexcept>

#include <stdlib.h>
#include <time.h>

#include <sodium/crypto_box.h>
#include <sodium/randombytes.h>

#include "macaroon/macaroon.h"

using namespace macaroons;

struct server {
	server(std::string n, std::string u, std::string p) {
		name = n;
		username = u;
		password = p;

		crypto_box_keypair(pk, sk);
		char secret[MACAROON_SUGGESTED_SECRET_LENGTH];
		randombytes((unsigned char*) secret, MACAROON_SUGGESTED_SECRET_LENGTH);

		secrets.push_back(secret);
	}

	macaroon login(std::string u, std::string p) {
		if (u == username && p == password) {
			macaroon m(name, secrets[0], std::to_string(0));

			std::stringstream predicate;
			predicate << "username = " << username << "@" << name;
			m.add_first_party_caveat(predicate.str());

			return m;
		}

		throw std::runtime_error("Authentication failed");
	}

	macaroon login(std::string identity, server remote) {
		macaroon m(name, secrets[0], std::to_string(0));

		std::stringstream predicate;
		predicate << "username = " << identity;
		m.add_first_party_caveat(predicate.str());

		m.add_third_party_caveat_with_keypair(remote.name, remote.pk, sk);

		return m;
	}

	macaroon login(std::string identity, macaroon m, macaroon md) {
		verifier v;

		std::stringstream predicate;
		predicate << "username = " << identity;
		v.satisfy_exact(predicate.str());

		predicate.str(std::string());
		predicate << "remote username = " << identity;
		v.satisfy_exact(predicate.str());

		// Push back the discharge macaroon
		macaroon_list l;
		l.push_back(md);

		std::string key = secrets[stoi(m.identifier())];
		if (!v.verify(m, key, l))
			throw std::runtime_error("login with remote discharge failed");

		macaroon ret(name, secrets[0], std::to_string(0));

		predicate.str(std::string());
		predicate << "username = " << identity;
		ret.add_first_party_caveat(predicate.str());

		return ret;
	}

	bool authenticate(std::string identity, macaroon m) {
		verifier v;

		std::stringstream predicate;
		predicate << "username = " << identity;
		v.satisfy_exact(predicate.str());

		std::string key = secrets[stoi(m.identifier())];

		return v.verify(m, key);
	}

	macaroon discharge(std::string identity, macaroon m, std::string s,
			server remote) {
		if (!authenticate(identity, m))
			throw std::runtime_error("Discharge authentication failed");

		macaroon ret = macaroon_discharge(name, s, remote.pk, sk);

		std::stringstream predicate;
		predicate << "remote username = " << identity;
		ret.add_first_party_caveat(predicate.str());

		return ret;
	}

	std::string username;
	std::string password;

	std::vector<std::string> secrets;
	std::string name;

	unsigned char pk[crypto_box_PUBLICKEYBYTES];
	unsigned char sk[crypto_box_SECRETKEYBYTES];
};

int main(void) {
	srand(time(NULL));

	server notabug("notabug.org", "alice", "password");
	server queertoo("queertoo.org", "bob", "password");

	// POST https://notabug.org/auth
	macaroon nab_m = notabug.login("alice", "password");

	// POST https://quertoo.org/auth/remote
	macaroon queertoo_notabug_m = queertoo.login("alice@notabug.org", notabug);

	third_party_caveat_list l = queertoo_notabug_m.third_party_caveats();

	// POST https://notabug.org/auth/discharge
	macaroon nab_md = notabug.discharge("alice@notabug.org", nab_m,
			l[0].identifier, queertoo);

	macaroon nab_queertoo_mds = queertoo_notabug_m.prepare_for_request(nab_md);

	// POST https://queertoo.org/auth/remote
	macaroon queertoo_m = queertoo.login("alice@notabug.org",
			queertoo_notabug_m, nab_queertoo_mds);

	// POST https://queertoo.org/discussions/0/comments
	if (queertoo.authenticate("alice@notabug.org", queertoo_m)) {
		std::cout << "Success" << std::endl;
		std::cout << queertoo_m.inspect() << std::endl;
	} else {
		std::cout << "Fail" << std::endl;
	}

	return EXIT_SUCCESS;
}
